#define STRICT
#define ORBITER_MODULE

#include "D3D11Client.h"
#include "GDIPad.h"
#include "Scene.h"
#include "Overlay.h"
#include "ParticleStream.h"
#include "Texture.h"
#include "vObject.h"
#include "Mesh.h"
#include "D3D11Pad.h"
#include "Version.h"

HINSTANCE hDLL;
D3D11Client *_gcl;

D3D11Client *gc = NULL;

#define APP_TITLE "Test - built on "__DATE__

//#pragma region Initialization
D3D11Client::D3D11Client( HINSTANCE hIn ) : oapi::GraphicsClient( hIn ) {
	gc = this;
	m_displayHUD = true;

	VTab = NULL;
	hWindow = NULL;
	bVideoTab = bEnumerate = bPopup = false;
	PCounter = new PerformanceCounter();
	cfg = new D3D11Config();
	DCount = 0;
	ThreadID = 0;
	HUDTEX = NULL;
	bBBRender = false;
	sscreen_init = false;
}

D3D11Client::~D3D11Client() {
	shaderManager.reset();
	iCtx->ClearState();
	iCtx->Flush();
	REL( iCtx );
	REL( Dev );
	delete PCounter;
	delete cfg;
	if( bVideoTab )
		delete VTab;
}

bool D3D11Client::clbkInitialise() {
	if( !oapi::GraphicsClient::clbkInitialise() )		return false;
	auto vdata = gc->GetVideoData();
	return true;
}

bool D3D11Client::clbkUseLaunchpadVideoTab() const {	
	return true;
}

HWND D3D11Client::clbkCreateRenderWindow() {
	WLOG2( APP_TITLE" is starting up..." );


	auto data = GetVideoData();
	hWindow = oapi::GraphicsClient::clbkCreateRenderWindow();
	/*auto currStyle = GetWindowLong(hWindow, GWL_STYLE);
	auto currExStyle = GetWindowLong(hWindow, GWL_EXSTYLE);
	auto style = WS_POPUP;
	auto exStyle = WS_EX_TOPMOST;*/
	SetWindowTextA( hWindow, "- "APP_TITLE );
	
	EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &m_currentMode);
	if (!data->fullscreen)
	{
		WINDOWINFO winfo;
		winfo.cbSize = sizeof( WINDOWINFO );
		GetWindowInfo( hWindow, &winfo );
		cfg->cWidth = abs( winfo.rcClient.right - winfo.rcClient.left );
		cfg->cHeight = abs( winfo.rcClient.bottom - winfo.rcClient.top );
	}
	else
	{
		cfg->cWidth = data->winw;
		cfg->cHeight = data->winh;
		auto currExStyle = GetWindowLong(hWindow, GWL_EXSTYLE);
		currExStyle |= WS_EX_TOPMOST;
		SetWindowLong(hWindow, GWL_EXSTYLE, currExStyle);
		if (m_currentMode.dmPelsWidth != cfg->cWidth || m_currentMode.dmPelsHeight != cfg->cHeight)
		{
			DEVMODE mode;
			CopyMemory(&mode, &m_currentMode, sizeof(DEVMODE));
			mode.dmBitsPerPel = 32;
			mode.dmPelsWidth = cfg->cWidth;
			mode.dmPelsHeight = cfg->cHeight;
			mode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
			ChangeDisplaySettings(&mode, 0);
			m_modeChanged = true;
		}
		MoveWindow(hWindow, 0, 0, cfg->cWidth, cfg->cHeight, true);
	}
	cfg->Aspect = (float)cfg->cWidth/(float)cfg->cHeight;

	if( !bEnumerate ) {
	//	Device enumeration always fails when placed before clbkCreateRenderWindow).
		cfg->EnumerateAll( GetVideoData() );
		cfg->LoadConfig();
		cfg->ApplyConfig();
		bEnumerate = true;
	}

	cfg->ApplyConfig();
	SC = new Scene( this, cfg );

	InitShaderProfiles(Dev->GetFeatureLevel());
	shaderManager.reset(new ShaderManager(Dev));

	SC->Init3D();

	TM = new TextureMgr();
	TM->GInit();
	SC->InitStatics();
	InitSplashScreen();	

	if( cfg->RThread ) {
		//creation of rendering thread.
		bRender = true;

		FrameReady = CreateEvent( NULL, TRUE, FALSE, "name 00" );
		SetEvent( FrameReady );		

		UpdateReady = CreateEvent( NULL, TRUE, FALSE, "name 11" );
		ResetEvent( UpdateReady );

		ResourceRequest = CreateEvent( NULL, TRUE, FALSE, NULL );
		ResetEvent( ResourceRequest );

		ResourceReleased = CreateEvent( NULL, TRUE, FALSE, NULL );
		ResetEvent( ResourceReleased );
		
		InitializeCriticalSection( &Resources );
	
		RenderThread = CreateThread( NULL, NULL, RenderThreadProc, SC, 0, &ThreadID );		
	}	

	OV = new Overlay();
	
	MM = new MeshManager();

	
	ProgressString( "write something here", 2 );
	ProgressString( "write something here 2", 0 );

//	ShowDefaultSplash();
//	if( cfg->RThread ) {
//		TM->ExecuteCommandList();
//	}
//	OV->RenderPBuffer();	//text.
//	SwapChain->Present( 0, 0 );	//show result

	return hWindow;
}

DWORD WINAPI RenderThreadProc( void *data ) {
	Scene *sc = (Scene*)data;

	WaitForSingleObject( UpdateReady, INFINITE );
	ResetEvent( UpdateReady );
	EnterCriticalSection( &Resources );

	while( bRender ) {
	/*
		Rendering thread procedure.

		Main thread can't use immediate ID3D11DeviceContext (thread unsafe) and only allowed to call ID3D11Device
		(thread safe, can be used from any number of threads at once), so any Blit and Fill call is to be
		placed in queue (see TextureManager) and then executed by rendering thread when required. ID3D11DeviceContext
		can be used	only from rendring thread or when rendering thread syncronized with main ( clbkRender2DPanel())

		OrbiterAPI's functions allowed only for SaveData() functions that will write data to some buffer in order to use Update()
		within rendering thread later.
	*/
		SC->Update();
		SC->Render_0();

		LeaveCriticalSection( &Resources );//allow texture modification.
		SetEvent( FrameReady );	//notify main thread that rendering thread completed its work and waits for update of data from Orbiter.

		WaitForSingleObject( UpdateReady, INFINITE );//rendering thread waits untill main thread renders 2D panel, executes all blit/fill functions in queue and updates object data.
		EnterCriticalSection( &Resources );//disallow texture modification
		ResetEvent( UpdateReady );

		SC->Render_1();
		SwapChain->Present( 0, 0 );
	}

	LeaveCriticalSection( &Resources );
	SetEvent( UpdateReady );

	return 0;
}

void D3D11Client::clbkUpdate( bool running ) {
	//everything is updated in clbkRenderScene() while rendering thread is stopped
}

void D3D11Client::clbkRenderScene() {
	static const LONG WStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE;	
	static DWORD nPopup, j;
	static const HWND *PopupList;
	auto data = GetVideoData();
//popup windows: ensure they all have close button.
	if( bPopup ) {
		nPopup = GetPopupList( &PopupList );
		if( nPopup && GetWindowLong( PopupList[j], GWL_STYLE ) != WStyle )
			for( j = 0; j < nPopup; j++ )
				SetWindowLong( PopupList[j], GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE );//add close button to all popup windows
		else
		{
			bPopup = false;
			if (data->fullscreen)
			{
				SwapChain->SetFullscreenState(TRUE, nullptr);
			}
		}
	}
//scene rendering.
	if( cfg->RThread ) {	//2-threaded mode	

		WaitForSingleObject( FrameReady, INFINITE );
		ResetEvent( FrameReady );
		if( sscreen_init )	ExitSplashScreen();

		SC->SaveParams();
		Render2DOverlay();

		SetEvent( UpdateReady );
	}
	else {					//single-threaded mode
		if( sscreen_init )	ExitSplashScreen();

		SC->SaveParams();
		SC->Update();

		SC->Render_0();
		SC->Render_1();
		SC->Render3DCompleted();
		if (m_displayHUD)
		{
			OV->RenderPBuffer();
#if _DEBUG
			D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0, 0x20), L"Render2DOverlay");
#endif
			Render2DOverlay();		
#if _DEBUG
			D3DPERF_EndEvent();
#endif
		}
		SwapChain->Present( data->novsync ? 0 : 1, 0 );
	}
}

bool D3D11Client::clbkDisplayFrame() {
	//not used
	return true;
}

void D3D11Client::clbkRender2DPanel( SURFHANDLE *srf, MESHHANDLE hMesh, MATRIX3 *T, bool transparent ) {
	if( cfg->RThread )	TM->ExecuteCommandList();//modify textures (mfd, hud etc.)
	SC->RenderOverlay( srf, hMesh, T, transparent );
}

void D3D11Client::WaitForFrameSync() {
/*
	Rare events. (new vessel, delete vessel etc.) Main thread waits for rendering thread's "FrameReady" message.
	though, that can be done by the same way, used for GDI...
*/
	if( cfg->RThread )
		WaitForSingleObject( FrameReady, INFINITE );
}

void D3D11Client::ToggleHUD()
{
	m_displayHUD = !m_displayHUD;
}

LRESULT D3D11Client::RenderWndProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) {
	if( msg == WM_KEYDOWN )
	{
		if( wp == VK_F11 ) 
		{
			bBBRender = !bBBRender;
			return FALSE;
		}
		else if (wp == VK_HOME && GetKeyState(VK_CONTROL))
		{
			gc->ToggleHUD();
		}
	}

	return oapi::GraphicsClient::RenderWndProc (hwnd, msg, wp, lp);
}

void D3D11Client::clbkPostCreation() {
	WLOG2( "D3D11Client::clbkPostCreation" );

/*
	WS_VISIBLE | WS_OVERLAPPEDWINDOW	"fullscreen mode"
	True fullscreen mode don't allow pop-up windows (Direct3D will switch back to windowed mode)
	GWL_STYLE, WS_VISIBLE | WS_OVERLAPPEDWINDOW		allows main window to be resized or extended to full screen.(BackBuffer resize not implemented so far)
*/
//	LONG data = SetWindowLong( hWindow, GWL_STYLE, WS_VISIBLE | WS_OVERLAPPEDWINDOW );
	SC->InitObjects();
}	

void D3D11Client::clbkPreOpenPopup() {
	bPopup = true;
	auto data = GetVideoData();
	if (data->fullscreen)
	{
		SwapChain->SetFullscreenState(FALSE, nullptr);
	}
}

void D3D11Client::clbkGetViewportSize( DWORD *w, DWORD *h ) const {
	WLOG2( "D3D11Client::clbkGetViewportSize" );
	*w = cfg->cWidth;
	*h = cfg->cHeight;
}

bool D3D11Client::clbkFullscreenMode() const {
	WLOG2( "D3D11Client::clbkFullscreenMode" );
	return cfg->FullScreenWindow;
}

bool D3D11Client::clbkGetRenderParam( DWORD param, DWORD *value ) const {
	switch( param ) {
		case RP_COLOURDEPTH:
			*value = 32;
			return true;
		case RP_ZBUFFERDEPTH:
			*value = 24;
			return true;
		case RP_STENCILDEPTH:
			*value = 8;
			return true;
		case RP_MAXLIGHTS:
			*value = 12;
			return true;
		case RP_ISTLDEVICE:
			*value = TRUE;
			return true;
		case RP_REQUIRETEXPOW2:
			*value = FALSE;
			return true;
	}
	return false;
}

//LAUNCHPAD.
BOOL D3D11Client::LaunchpadVideoWndProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) {
	if( !bVideoTab ) {
		if( !bEnumerate ) {
			cfg->EnumerateAll( GetVideoData() );
			cfg->LoadConfig();
			cfg->ApplyConfig();
			bEnumerate = true;
		}
		bVideoTab = true;
		VTab = new VideoTab( this, cfg, oapi::GraphicsClient::LaunchpadVideoTab(), hDLL );
		VTab->InitTextBoxes();
	}
	if (VTab)
	{
		return VTab->LaunchpadVideoWndProc( hwnd, msg, wp, lp );
	}
	else
	{
		return oapi::GraphicsClient::LaunchpadVideoWndProc(hwnd, msg, wp, lp);
	}
}

void D3D11Client::clbkRefreshVideoData() {
	if( VTab )
		VTab->UpdateConfig();
}

void D3D11Client::clbkCloseSession( bool fastclose ) {
	WLOG2( "D3D11Client::clbkCloseSession" );
	oapi::GraphicsClient::clbkCloseSession( fastclose );
}

void D3D11Client::clbkDestroyRenderWindow( bool fastclose ) {
	WLOG2( "D3D11Client::clbkDestroyRenderWindow" );
//terminate rendering thread.
	if (m_modeChanged)
	{
		ChangeDisplaySettings(&m_currentMode, 0);
	}
	SC->Exit3D();
	bRender = false;
	if( cfg->RThread ) {
		WaitForSingleObject( UpdateReady, INFINITE );
		Sleep( 10 );
		TerminateThread( RenderThread, 0xF );
		CloseHandle( FrameReady );
		CloseHandle( UpdateReady );
		CloseHandle( ResourceRequest );
		CloseHandle( ResourceReleased );
		DeleteCriticalSection( &Resources );
	}
//cleaning up scene.
	delete SC;
	delete OV;
	delete TM;
	delete MM;
	oapi::GraphicsClient::clbkDestroyRenderWindow( fastclose );
}

#pragma region Mesh and Object Functions
//vessels.
int D3D11Client::clbkVisEvent( OBJHANDLE hObj, VISHANDLE vis, DWORD msg, UINT context ) {
	if( cfg->RThread ) {
		SetEvent( ResourceRequest );
		EnterCriticalSection( &Resources );

		int result = ((vObject*)vis)->VisEvent( msg, context );

		LeaveCriticalSection( &Resources );
		SetEvent( ResourceReleased );
		return result;
	}
	vVessel *vobj = (vVessel*)vis;
	int idx = -1;
	for( DWORD j = 0; j < SC->VesselCount; j++ ) {
		if( SC->Vessel[j] == vobj ) {
			idx = j;
			break;
		}
	}

	if( idx != -1 )		return ((vObject*)vis)->VisEvent( msg, context );
	else				return 0;
}

void D3D11Client::clbkNewVessel( OBJHANDLE obj ) {
	if( cfg->RThread ) {	//sync with resources.
		SetEvent( ResourceRequest );
		EnterCriticalSection( &Resources );

		SC->NewVessel( obj );

		LeaveCriticalSection( &Resources );
		SetEvent( ResourceReleased );
		return;
	}
	SC->NewVessel( obj );
}

void D3D11Client::clbkDeleteVessel( OBJHANDLE hVessel ) {
	if( cfg->RThread ) {	//sync with resources.
		SetEvent( ResourceRequest );
		EnterCriticalSection( &Resources );

		SC->DeleteVessel( hVessel );

		LeaveCriticalSection( &Resources );
		SetEvent( ResourceReleased );
		return;
	}
	SC->DeleteVessel( hVessel );
}

MESHHANDLE D3D11Client::clbkGetMesh( VISHANDLE vis, UINT idx ) {
	if( !vis )		return NULL;
	return ((vObject*)vis)->clbkGetMesh( idx );
}

int D3D11Client::clbkEditMeshGroup( DEVMESHHANDLE hMesh, DWORD grpidx, GROUPEDITSPEC *ges ) {
	if( cfg->RThread ) {	//sync with resources.
		SetEvent( ResourceRequest );
		EnterCriticalSection( &Resources );

		int result = ((D3D11TPLMesh*)hMesh)->EditGroup( grpidx, ges );

		LeaveCriticalSection( &Resources );
		SetEvent( ResourceReleased );
		return result;
	}
	return ((D3D11TPLMesh*)hMesh)->EditGroup( grpidx, ges );
}

bool D3D11Client::clbkSetMeshTexture( DEVMESHHANDLE hMesh, DWORD texidx, SURFHANDLE tex ) {

	oapiWriteLogV("clbkSetMeshTexture called !");

	if( cfg->RThread ) {
		SetEvent( ResourceRequest );
		EnterCriticalSection( &Resources );

		bool result = ((D3D11TPLMesh*)hMesh)->SetMeshTexture( texidx, (Texture*)tex );

		LeaveCriticalSection( &Resources );
		SetEvent( ResourceReleased );
		return result;
	}
	return ((D3D11TPLMesh*)hMesh)->SetMeshTexture( texidx, (Texture*)tex );
}

int D3D11Client::clbkSetMeshMaterial( DEVMESHHANDLE hMesh, DWORD matidx, const MATERIAL *mat ) {
	if( cfg->RThread ) {	//sync with resources.
		SetEvent( ResourceRequest );
		EnterCriticalSection( &Resources );

		int result = ((D3D11TPLMesh*)hMesh)->SetMeshMaterial( matidx, mat );

		LeaveCriticalSection( &Resources );
		SetEvent( ResourceReleased );
		return result;
	}
	return ((D3D11TPLMesh*)hMesh)->SetMeshMaterial( matidx, mat );
}

bool D3D11Client::clbkSetMeshProperty( DEVMESHHANDLE hMesh, DWORD prop, DWORD value ) {
	if( cfg->RThread ) {	//sync with resources.
		SetEvent( ResourceRequest );
		EnterCriticalSection( &Resources );

		bool result = ((D3D11TPLMesh*)hMesh)->SetMeshProperty( prop, value );

		LeaveCriticalSection( &Resources );
		SetEvent( ResourceReleased );
		return result;
	}
	return ((D3D11TPLMesh*)hMesh)->SetMeshProperty( prop, value );
}

void D3D11Client::clbkStoreMeshPersistent( MESHHANDLE hMesh, const char *fname ) {
	//no need to sync.

	oapiWriteLogV("MESH loaded from file : %s", fname);

	MM->Store( hMesh );
}
#pragma endregion
#pragma region TextureFunctions
SURFHANDLE D3D11Client::clbkLoadTexture( const char *fname, DWORD flags ) {	
	return (SURFHANDLE)TM->LoadTextureFromFile( fname, flags );
}

SURFHANDLE D3D11Client::clbkLoadSurface (const char *fname, DWORD attrib)
{
	return (SURFHANDLE)TM->LoadTextureFromFile( fname, 0 );
}

SURFHANDLE D3D11Client::clbkCreateTexture( DWORD w, DWORD h ) {
	return (SURFHANDLE)TM->CreateSurface( w, h );
}

void D3D11Client::clbkReleaseTexture( SURFHANDLE tex ) {
	TM->ReleaseTexture( (Texture*)tex );
}

HDC D3D11Client::clbkGetSurfaceDC( SURFHANDLE surf ) {
	return TM->GetSurfaceDC( (Texture*)surf );
}

void D3D11Client::clbkReleaseSurfaceDC( SURFHANDLE surf, HDC hdc ) {
	TM->ReleaseSurfaceDC( hdc, (Texture*)surf );
}

bool D3D11Client::clbkReleaseSurface( SURFHANDLE surf ) {
	TM->ReleaseTexture( (Texture*)surf );
	return true;
}

SURFHANDLE D3D11Client::clbkCreateSurface( DWORD w, DWORD h, SURFHANDLE hTempl ) {
	return (SURFHANDLE)TM->CreateSurface( w, h );
}

SURFHANDLE D3D11Client::clbkCreateSurfaceEx (DWORD w, DWORD h, DWORD attrib)
{
	return (SURFHANDLE)TM->CreateSurfaceEx(w, h, attrib);
}

SURFHANDLE D3D11Client::clbkCreateSurface( HBITMAP hbm ) {
	return (SURFHANDLE)TM->CreateSurfaceBitmap( hbm );
}

bool D3D11Client::clbkGetSurfaceSize( SURFHANDLE surf, DWORD *w, DWORD *h ) {
	TM->GetSize( (Texture*)surf, w, h );
	return true;
}

bool D3D11Client::clbkCopyBitmap( SURFHANDLE pdds, HBITMAP hbm, int x, int y, int dx, int dy ) {
	return TM->CopyBitmap( (Texture*)pdds, hbm, x, y, dx, dy );
}

bool D3D11Client::clbkBlt( SURFHANDLE tgt, DWORD tgtx, DWORD tgty, SURFHANDLE src, DWORD flags ) const {
	return TM->Blit1( (Texture*)tgt, tgtx, tgty, (Texture*)src, flags );
}

bool D3D11Client::clbkBlt( SURFHANDLE tgt, DWORD tgtx, DWORD tgty, SURFHANDLE src, DWORD srcx, DWORD srcy, DWORD w, DWORD h, DWORD flags ) const {
	return TM->Blit2( (Texture*)tgt, tgtx, tgty,  (Texture*)src, srcx, srcy, w, h, flags );
}

bool D3D11Client::clbkScaleBlt( SURFHANDLE tgt, DWORD tgtx, DWORD tgty, DWORD tgtw, DWORD tgth, SURFHANDLE src, DWORD srcx, DWORD srcy, DWORD srcw, DWORD srch, DWORD flags ) const {
	return TM->ScaleBlit( (Texture*)tgt, tgtx, tgty, tgtw, tgth, (Texture*)src, srcx, srcy, srcw, srch, flags );
}

bool D3D11Client::clbkFillSurface( SURFHANDLE surf, DWORD col ) const {
	return TM->FillSurface( (Texture*)surf, col );
}

bool D3D11Client::clbkFillSurface( SURFHANDLE surf, DWORD tgtx, DWORD tgty, DWORD w, DWORD h, DWORD col ) const {
	return TM->FillSurfaceRect( (Texture*)surf, tgtx, tgty, w, h, col );
}

DWORD D3D11Client::clbkGetDeviceColour( BYTE r, BYTE g, BYTE b ) {
	return TM->GetDevColor( r, g, b );
}

bool D3D11Client::clbkSetSurfaceColourKey( SURFHANDLE surf, DWORD ckey ) {
	if( surf )
		return TM->SetColorKey( (Texture*)surf, ckey );
	return false;
}

void D3D11Client::clbkIncrSurfaceRef( SURFHANDLE surf ) {
	TM->IncrSurfaceRef( (Texture*)surf );
}
#pragma endregion surface and texture functions
#pragma region Sketchpad/Fonts/Brushes/Pens
//GDI.
oapi::Sketchpad *D3D11Client::clbkGetSketchpad( SURFHANDLE surf ) {
//	Creates one of 2 possible versions of 2D Sketchpad
	HDC hDC;
	Texture *tex = (Texture*)surf;
	if( !tex )	SC->BBDrawCount++;

	if( HUDTEX && HUDTEX == tex ) 
	{
		SC->PBuffers_counter++;
		if( cfg->RThread ) 
		{
			SetEvent( ResourceRequest );
			EnterCriticalSection( &Resources );
		}
		return new D3D11Pad( surf, TM );
	}

	if( cfg->SketchPadMode == 0 ) {		//GDI only	[mode 0]
		hDC = clbkGetSurfaceDC( surf );
		if( hDC )
			return new GDIPad( surf, hDC );
		else
			return NULL;
	}

	if( cfg->SketchPadMode == 1 ) {		//GDI for surfaces, D3D11Pad for backbuffer	[mode 1]
		if( tex ) {
			hDC = clbkGetSurfaceDC( surf );
			if( hDC )
				return new GDIPad( surf, hDC );
			else
				return NULL;
		}
		else {
			SC->PBuffers_counter++;
			if( cfg->RThread ) {
				SetEvent( ResourceRequest );
				EnterCriticalSection( &Resources );
			}
			return new D3D11Pad( surf, TM );
		}
	}

	if( cfg->SketchPadMode > 1 ) {		//D3D11Pad for back buffer and surfaces both	[mode 2]
		if( !TM->Find( tex ) )
			return NULL;

		if( !tex ) {
			SC->PBuffers_counter++;
			if( cfg->RThread ) {
				SetEvent( ResourceRequest );
				EnterCriticalSection( &Resources );
			}
			return new D3D11Pad( surf, TM );
		}
		else {
			if( tex->IsTex() )
				TM->ConvertTextureToSurface( tex );
			if( cfg->RThread ) {
				SetEvent( ResourceRequest );
				EnterCriticalSection( &Resources );
				TM->ExecuteCommandList();
			}
			D3D11Pad *t = new D3D11Pad( surf, TM );
			return t;
		}
	}
	return NULL;
}

void D3D11Client::clbkReleaseSketchpad( oapi::Sketchpad *Skpad ) {
	if( !Skpad )
		return;

	GDIPad *gdi_pad;
	D3D11Pad *d3d11_pad;

	if( cfg->SketchPadMode == 0 ) {			//GDI only	[mode 0]
		gdi_pad = (GDIPad*)Skpad;
		clbkReleaseSurfaceDC( gdi_pad->GetSurface(), gdi_pad->GetDC() );
		delete gdi_pad;
		return;
	}

	if( cfg->SketchPadMode == 1 ) {			//GDI for surfaces, D3D11Pad for backbuffer	[mode 1]
		SURFHANDLE surf = Skpad->GetSurface();
		if( surf ) 
		{
			if( surf == HUDTEX ) 
			{
				d3d11_pad = (D3D11Pad*)Skpad;
				delete d3d11_pad;
				return;
			}

			gdi_pad = (GDIPad*)Skpad;
			clbkReleaseSurfaceDC( gdi_pad->GetSurface(), gdi_pad->GetDC() );
			delete gdi_pad;
			return;
		}
		else {
			d3d11_pad = (D3D11Pad*)Skpad;
			if( cfg->RThread ) {
				LeaveCriticalSection( &Resources );
				SetEvent( ResourceReleased );
			}
			delete d3d11_pad;
			return;
		}
	}

	if( cfg->SketchPadMode > 1 ) {			//D3D11Pad for back buffer and surfaces both	[mode 2]
		d3d11_pad = (D3D11Pad*)Skpad;
		if( cfg->RThread ) {
			LeaveCriticalSection( &Resources );
			SetEvent( ResourceReleased );
		}
		delete d3d11_pad;
		return;
	}
}

oapi::Font *D3D11Client::clbkCreateFont( int height, bool prop, const char *face, oapi::Font::Style style, int orientation ) const {
	return new D3D11PadFont( height, prop, face, style, orientation );
//	return new GDIFont( height, prop, face, style, orientation );
}

void D3D11Client::clbkReleaseFont( oapi::Font *font ) const {
	delete ((D3D11PadFont*)font);
//	delete font;
}

oapi::Pen *D3D11Client::clbkCreatePen( int style, int width, DWORD col ) const {
	return new D3D11PadPen( style, width, col );
//	return new GDIPen( style, width, col );
}

void D3D11Client::clbkReleasePen( oapi::Pen *pen ) const {
	delete ((D3D11PadPen*)pen);
//	delete pen;
}

oapi::Brush *D3D11Client::clbkCreateBrush( DWORD col ) const {
	return new D3D11PadBrush( col );
//	return new GDIBrush( col );
}

void D3D11Client::clbkReleaseBrush( oapi::Brush *brush ) const {
	delete ((D3D11PadBrush*)brush);
//	delete brush;
}

#pragma endregion GDI drawing functions
#pragma region PStreamFunctions
oapi::ParticleStream *D3D11Client::clbkCreateExhaustStream( PARTICLESTREAMSPEC *pss, OBJHANDLE obj, const double *lvl, const VECTOR3 *ref, const VECTOR3 *dir ) {
	
	ExhaustStream *es = new ExhaustStream( this, obj, lvl, ref, dir, pss );
	SC->AddParticleStream( es );
	return es;
}

oapi::ParticleStream *D3D11Client::clbkCreateExhaustStream( PARTICLESTREAMSPEC *pss, OBJHANDLE obj, const double *lvl, const VECTOR3 &ref, const VECTOR3 &dir ) {
	
	ExhaustStream *es = new ExhaustStream( this, obj, lvl, ref, dir, pss );
	SC->AddParticleStream( es );
	return es;
}

oapi::ParticleStream *D3D11Client::clbkCreateReentryStream( PARTICLESTREAMSPEC *pss, OBJHANDLE obj ) {
	ReentryStream *rs = new ReentryStream( this, pss, obj );
	SC->AddParticleStream( rs );
	return rs;
}

bool D3D11Client::clbkParticleStreamExists( const oapi::ParticleStream *ps ) {
	return false;
}
#pragma endregion Particle stream functions (temporarily void)

oapi::ScreenAnnotation *D3D11Client::clbkCreateAnnotation() {
	return oapi::GraphicsClient::clbkCreateAnnotation();
}

//module functions.
DLLCLBK void InitModule( HINSTANCE h )
{
	HMODULE hd3d11dll = ::LoadLibrary("d3d11.dll");
	if (!hd3d11dll)
	{
		::MessageBox(0, "This module requires DirectX 11 to be installed.", "DirectX 11 is not installed", MB_OK | MB_ICONERROR);
		return;
	}
	::FreeLibrary(hd3d11dll);
	hDLL = h;
	_gcl = new D3D11Client( hDLL );	
	if( !oapiRegisterGraphicsClient( _gcl ) ) {
		delete _gcl;
		_gcl = NULL;
	}
}

DLLCLBK void ExitModule(HINSTANCE hDLL){
	if( _gcl ) {
		oapiUnregisterGraphicsClient( _gcl );
		_gcl = NULL;
	}
}

void D3D11Client::InitSplashScreen() {
	HBITMAP hbm = LoadBitmap( oapiGetOrbiterInstance(), MAKEINTRESOURCE(273) );
	SplashScreen = TM->CreateSurfaceBitmap( hbm );
	text_Surface = TM->CreateSurface( 512, 512 );
	TM->FillSurface( text_Surface, 0x00 );
	ypos = 16;
	text_Font = oapiCreateFont( 14, true, "Consolas", FONT_NORMAL, 0 );

	quad[0].pos = D3DXVECTOR2( 0.05f, 0.4f );
	quad[1].pos = D3DXVECTOR2( 0.05f, 0.9f );
	quad[2].pos = D3DXVECTOR2( 0.6f, 0.9f );
	quad[3].pos = D3DXVECTOR2( 0.6f, 0.4f );

	quad[0].tex = D3DXVECTOR2( 0.0f, 0.03125f );
	quad[1].tex = D3DXVECTOR2( 0.0f, 1.0f );
	quad[2].tex = D3DXVECTOR2( 1.0f, 1.0f );
	quad[3].tex = D3DXVECTOR2( 1.0f, 0.03125f );

	sscreen_init = true;
}

void D3D11Client::ProgressString( const char *line, DWORD color ) {
	static const double delta = 0.03125;

	oapiWriteLogV("ProgressString :  %s", line);

	if( !sscreen_init )	return;

	//draw progress string on splash screen
	switch( color ) {
	case 0:
		color = 0xFFFFFF;	//white
		break;
	case 1:
		color = 0xCFFFCF;	//light-green
		break;
	case 2:
		color = 0xAFCFFF;	//orange
		break;
	case 3:
		color = 0xFF;		//red
		break;
	case 4:
		color = 0xA0FFA0;	//green
		break;
	case 5:
		color = 0xFFA0A0;	//blue
		break;
	}

	//write progress string
	D3D11Pad *Skpad = new D3D11Pad( (SURFHANDLE)text_Surface, TM );
	Skpad->SetFont( text_Font );
	Skpad->SetTextColor( color );
	Skpad->SetTextAlign( oapi::Sketchpad::LEFT, oapi::Sketchpad::BOTTOM );
	Skpad->SetBackgroundMode( oapi::Sketchpad::BK_TRANSPARENT );
	Skpad->Text( 5, ypos, line, strlen(line) );
	Skpad->SetFont( NULL );
	delete Skpad;

	//draw splash screen
	DWORD w, h;
	SplashScreen->GetSize( &w, &h );
	TM->ScaleBlit( NULL, 0, 0, cfg->cWidth, cfg->cHeight, SplashScreen, 0, 0, w, h, 0 );
	
	static bool shift = false;
	ypos += 16;
	if( ypos > 512 ) {
		shift = true;
		ypos -= 512;
	}

	D3D11_MAPPED_SUBRESOURCE SRes;
	memset( &SRes, 0, sizeof(SRes) );
	iCtx->Map( TM->VB, 0, D3D11_MAP_WRITE_DISCARD, 0, &SRes );
	memcpy( SRes.pData, quad, 64 );
	iCtx->Unmap( TM->VB, 0 );

	const UINT VtxStride = 16, VBOffset = 0;	
	iCtx->IASetVertexBuffers( 0, 1, &TM->VB, &VtxStride, &VBOffset );
	iCtx->IASetIndexBuffer( TM->IB, DXGI_FORMAT_R16_UINT, 0 );
	iCtx->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP );
	iCtx->IASetInputLayout( TM->IL_Blit );
	iCtx->VSSetConstantBuffers( 0, 1, &TM->cb_VS );
	iCtx->UpdateSubresource( TM->cb_VS, 0, NULL, &TM->Orth, 0, 0 );
	iCtx->VSSetShader( TM->VS_Blit, NULL, NULL );
	iCtx->PSSetConstantBuffers( 1, 1, &TM->cb_PS );
	iCtx->PSSetShaderResources( 0, 1, text_Surface->GetSRV() );
	iCtx->PSSetSamplers( 0, 1, &SS_Point_Wrap );
	iCtx->UpdateSubresource( TM->cb_PS, 0, NULL, D3DXVECTOR4( 0.3f, 0.3f, 0.3f, 2.0f ), 0, 0 );
	iCtx->PSSetShader( TM->PS_Blit, NULL, NULL );	
	iCtx->OMSetBlendState( BS_InvSrcAlpha, D3DXVECTOR4( 1, 1, 1, 1 ), 0xFFFFFFFF );
	iCtx->OMSetDepthStencilState( DSS_NoDepth_NoStencil, 0 );
	iCtx->RSSetState( RS_CullNone_Solid );

	D3D11_VIEWPORT vp;
	vp.Width = (float)cfg->cWidth;
	vp.Height = (float)cfg->cHeight;
	vp.TopLeftX = 0;
	vp.TopLeftY = 0;
	vp.MaxDepth = 1;
	vp.MinDepth = 0;

	iCtx->RSSetViewports( 1, &vp );
	iCtx->DrawIndexed( 6, 0, 0 );

	OV->RenderPBuffer();

	//output result
	SwapChain->Present( 0, 0 );

	if( shift ) {
		quad[0].tex.y += (float)delta;
		quad[1].tex.y += (float)delta;
		quad[2].tex.y += (float)delta;
		quad[3].tex.y += (float)delta;
	}

	TM->FillSurfaceRect( text_Surface, 0, ypos-16, 512, 16, 0x0 );
}

void D3D11Client::ExitSplashScreen() {
	delete text_Font;
	TM->ReleaseTexture( text_Surface );
	TM->ReleaseTexture( SplashScreen );
	ypos = 16;
	sscreen_init = false;
	text_Surface = SplashScreen = NULL;
}
